package com.luo.d3s.ext.event.jdbc.publish;

import com.luo.d3s.core.domain.event.DomainEvent;
import com.luo.d3s.core.domain.event.DomainEventRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;

/**
 * 领域事件发送器 - 存储转发实现（支持DB事务提交后触发事件发送）
 *
 * @author luohq
 * @date 2022-12-22 13:55
 */
@Component
public class DomainEventTxPublisher {

    protected static final Logger log = LoggerFactory.getLogger(DomainEventTxPublisher.class);

    @Resource
    private DomainEventRepository domainEventRepository;


    @Resource
    private DomainEventPublisherDispatcher domainEventPublisherDispatcher;


    /**
     * 存储并转发领域事件（支持DB事务提交后触发事件发送）
     *
     * @param domainEvent 领域事件
     */
    public void saveAndSend(DomainEvent domainEvent) {
        //在当前DB事务中持久化领域事件（10:已创建、未发送状态）
        this.saveDb(domainEvent);
        //事务成功提交后，发送事件并修改发送成功状态（20:已发送）
        this.addTxCallback(domainEvent);
    }

    /**
     * 添加事务成功回调（发送事件并修改发送成功状态（20:已发送））
     *
     * @param domainEvent 领域事件
     */
    private void addTxCallback(DomainEvent domainEvent) {
        //若存在事务上下文，则添加监听器，在事务提交后触发后续任务
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronization transactionSynchronization = new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    //发送事件 并 修改发送成功状态（20:已发送）
                    send(domainEvent);
                    updateDbEventStatus(domainEvent);
                }
            };
            TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
            log.info("Success to register Transaction Synchronization for DomainEvent: {}", domainEvent);
        } else {
            //不存在事务上下文，直接触发后续任务
            log.info("No Transaction and Send Directly for DomainEvent: {}", domainEvent);
            //发送事件 并 修改发送成功状态（20:已发送）
            send(domainEvent);
            updateDbEventStatus(domainEvent);
        }
    }

    /**
     * 在当前DB事务中持久化领域事件（10:已创建、未发送状态）
     *
     * @param domainEvent 领域事件
     */
    private void saveDb(DomainEvent domainEvent) {
        //在业务事务中保存事件
        this.domainEventRepository.save(domainEvent);
    }

    /**
     * 发送领域事件
     *
     * @param domainEvent 领域事件
     */
    private void send(DomainEvent domainEvent) {
        //根据事件类型调用不同的发送器
        this.domainEventPublisherDispatcher.publishByType(domainEvent);
    }

    /**
     * 修改DB事件发送成功状态（20:已发送）
     *
     * @param domainEvent 领域事件
     */
    private void updateDbEventStatus(DomainEvent domainEvent) {
        this.domainEventRepository.published(domainEvent);
    }

}
